import Inula from "openinula";
import addOneClass from "dom-helpers/addClass";
import removeOneClass from "dom-helpers/removeClass";
import { style as setStyle } from "dom-helpers";
import Transition from "./Transition";

const addClass = (node, classes) => node && classes && classes.split(" ").forEach((c) => addOneClass(node, c));
const removeClass = (node, classes) => node && classes && classes.split(" ").forEach((c) => removeOneClass(node, c));

/**
 * 使用 https://animate.style/ 动画样式类的过渡动画实现。
 *
 * ```jsx
 * function App() {
 *   const [inProp, setInProp] = useState(false);
 *   const nodeRef = useRef(null);
 *   return (
 *     <div>
 *       <AnimateTransition nodeRef={nodeRef} in={inProp} timeout={200} classNames="animate__fadeIn">
 *         <div ref={nodeRef}>
 *           {"I'll receive animate__fadeIn classes"}
 *         </div>
 *       </AnimateTransition>
 *       <button type="button" onClick={() => setInProp(true)}>
 *         Click to Enter
 *       </button>
 *     </div>
 *   );
 * }
 * ```
 */

class AnimateTransition extends Inula.Component {
  static defaultProps = {
    classNames: "",
  };

  appliedClasses = {
    enter: null,
    exit: null,
  };

  onEnter = (maybeNode, maybeAppearing) => {
    const { timeout, appear } = this.props;
    const [node, appearing] = this.resolveArguments(maybeNode, maybeAppearing);

    this.removeClasses(node, "exit");

    if (!appear || (appear && appearing)) {
      this.addClass(node, "enter");

      setStyle(node, {
        "animation-duration": `${timeout}ms`,
      });
    }

    if (this.props.onEnter) {
      this.props.onEnter(maybeNode, maybeAppearing);
    }
  };

  onEntering = (maybeNode, maybeAppearing) => {
    if (this.props.onEntering) {
      this.props.onEntering(maybeNode, maybeAppearing);
    }
  };

  onEntered = (maybeNode, maybeAppearing) => {
    const { appear } = this.props;
    const [node, appearing] = this.resolveArguments(maybeNode, maybeAppearing);

    if (!appear || (appear && appearing)) {
      this.removeClasses(node, "enter");

      setStyle(node, {
        "animation-duration": null,
      });
    }

    if (this.props.onEntered) {
      this.props.onEntered(maybeNode, maybeAppearing);
    }
  };

  onExit = (maybeNode) => {
    const { timeout } = this.props;
    const [node] = this.resolveArguments(maybeNode);

    this.addClass(node, "exit");

    setStyle(node, {
      "animation-duration": `${timeout}ms`,
    });

    if (this.props.onExit) {
      this.props.onExit(maybeNode);
    }
  };

  onExiting = (maybeNode) => {
    if (this.props.onExiting) {
      this.props.onExiting(maybeNode);
    }
  };

  onExited = (maybeNode) => {
    const [node] = this.resolveArguments(maybeNode);

    this.removeClasses(node, "exit");

    setStyle(node, {
      "animation-duration": null,
    });

    if (this.props.onExited) {
      this.props.onExited(maybeNode);
    }
  };

  // when prop `nodeRef` is provided `node` is excluded
  resolveArguments = (maybeNode, maybeAppearing) =>
    this.props.nodeRef
      ? [this.props.nodeRef.current, maybeNode] // here `maybeNode` is actually `appearing`
      : [maybeNode, maybeAppearing]; // `findDOMNode` was used

  addClass(node, type) {
    const { classNames } = this.props;

    if (classNames) {
      const className = classNames.indexOf("animate__") > -1 && classNames.indexOf("animate__animated") < 0 ? `animate__animated ${classNames}` : classNames;

      this.appliedClasses[type] = className;
      addClass(node, className);
    }
  }

  removeClasses(node, type) {
    const className = this.appliedClasses[type];

    this.appliedClasses[type] = null;

    if (className) {
      removeClass(node, className);
    }
  }

  render() {
    const { classNames: _, ...props } = this.props;

    return (
      <Transition
        {...props}
        onEnter={this.onEnter}
        onEntering={this.onEntering}
        onEntered={this.onEntered}
        onExit={this.onExit}
        onExiting={this.onExiting}
        onExited={this.onExited}
      />
    );
  }
}

export default AnimateTransition;
